#include "roombuilders.h"
#include "random.h"
#include "map.h"

void RBBoxRoom::GetRoomSize( int& width, int& height ) const
{
    width  = Random::Int( 12 ) + 4;
    height = Random::Int( 12 ) + 4;
}

void RBBoxRoom::BuildInternal( int width, int height )
{
    int lMargin = Random::Int( 3 );
    int rMargin = Random::Int( 3 );
    int tMargin = Random::Int( 3 );
    int bMargin = Random::Int( 3 );
    while ( lMargin + rMargin >= width - 4 ) {
        if ( Random::Int( 2 ) && lMargin > 0 )
            lMargin--;
        else
            rMargin--;
    }
    while ( tMargin + bMargin >= height - 4 ) {
        if ( Random::Int( 2 ) && tMargin > 0 )
            tMargin--;
        else
            bMargin--;
    }
    lMargin = std::max( 0, lMargin );
    rMargin = std::max( 0, rMargin );
    tMargin = std::max( 0, tMargin );
    bMargin = std::max( 0, bMargin );
    left   = lMargin;
    right  = width - rMargin;
    top    = tMargin;
    bottom = height - bMargin;
    for ( int y = top; y < bottom; y++ ) {
        for ( int x = left; x < right; x++ )
            SetTile( x, y, Tile::FLOOR );
    }
}

void RBPassage::GetRoomSize( int& width, int& height ) const
{
    width  = Random::Int( 8 ) + 4;
    height = Random::Int( 8 ) + 4;
}

void RBPassage::BuildInternal( int width, int height )
{
    int w = Random::Int( 2 ) + 1;
    int h = Random::Int( 2 ) + 1;

    int px = Random::Int( 3 ) - 1;
    int py = Random::Int( 3 ) - 1;

    for ( int x = std::max( width / 2 + px, 0 ); x < width / 2 + px + w && x < width; x++ ) {
        for ( int y = std::max( height / 2 + py, 0 ); y < height / 2 + py + h && y < height; y++ ) {
            SetTile( x, y, Tile::FLOOR );
        }
    }
}

void RBPillarRoom::GetRoomSize( int& width, int& height ) const
{
    width  = Random::Int( 8 ) + 8;
    height = Random::Int( 8 ) + 8;
}

void RBPillarRoom::BuildInternal( int width, int height )
{
    RBBoxRoom::BuildInternal( width, height );
    int size = Random::Int( 2 ) + 1;
    int gap  = size + Random::Int( 3 );
    int xOffset = Random::Int( size + gap );
    int yOffset = Random::Int( size + gap );

    for ( int x = left; x < right; x++ ) {
        for ( int y = top; y < bottom; y++ ) {
            if ( ( x + xOffset ) % ( size + gap ) < size &&
                 ( y + yOffset ) % ( size + gap ) < size )
                SetTile( x, y, Tile::WALL );
        }
    }
}

void RBSubRoom::GetRoomSize( int& width, int& height ) const
{
    width  = Random::Int( 8 ) + 8;
    height = Random::Int( 8 ) + 8;
}

void RBSubRoom::BuildInternal( int width, int height )
{
    RBBoxRoom::BuildInternal( width, height );
    if ( bottom - top - 2 <= 0 || right - left - 2 <= 0 )
        return;

    // Build a box in the middle
    int ySize = Random::Int( std::max( 0, bottom - top - 2 ) ) + 1;
    int xSize = Random::Int( std::max( 0, right - left - 2 ) ) + 1;
    int yPos  = Random::Int( bottom - top - ySize - 1 );
    int xPos  = Random::Int( right - left - xSize - 1 );

    for ( int x = left + 1 + xPos; x < left + 1 + xPos + xSize; x++ ) {
        for ( int y = top + 1 + yPos; y < top + 1 + yPos + ySize; y++ ) {
            SetTile( x, y, Tile::WALL );
        }
    }

    if ( Random::Int( 2 ) )
        return;

    // Carve some more stuff out of it
    int thickness = Random::Int( 3 ) + 1;
    if ( thickness * 2 >= xSize || thickness * 2 >= ySize )
        return;
    int l = 0, r = 0, t = 0, b = 0;
    while ( l >= r ) {
        l = Random::Int( xSize - thickness * 2 ) + left + 1 + xPos + thickness;
        r = Random::Int( xSize - thickness * 2 ) + left + 2 + xPos + thickness;
    }
    while ( t >= b ) {
        t = Random::Int( ySize - thickness * 2 ) + top  + 1 + yPos + thickness;
        b = Random::Int( ySize - thickness * 2 ) + top  + 2 + yPos + thickness;
    }
    int rand = Random::Int( 3 );
    bool vert = rand < 2;
    bool horz = rand > 0;

    for ( int x = left + 1 + xPos; x < left + 1 + xPos + xSize; x++ ) {
        for ( int y = top + 1 + yPos; y < top + 1 + yPos + ySize; y++ ) {
            bool bx = x >= left + 1 + xPos + thickness && x <  left + 1 + xPos + xSize - thickness;
            bool by = y >= top  + 1 + yPos + thickness && y <  top  + 1 + yPos + ySize - thickness;

            if ( ( bx && by ) || ( x >= l && x < r && horz ) || ( y >= t && y < b && vert ) )
                SetTile( x, y, Tile::FLOOR );
        }
    }
}

void RBAutomataCave::GetRoomSize( int& width, int& height ) const
{
    width  = Random::Int( 16 ) + Random::Int( 16 ) + 8;
    height = Random::Int( 16 ) + Random::Int( 16 ) + 8;
}

void RBAutomataCave::BuildInternal( int width, int height )
{
    bool* front = new bool[ width * height ];
    bool* back  = new bool[ width * height ];

    bool okay = false;
    while ( !okay ) {

        // Initial randomisation
        for ( int x = 0; x < width; x++ ) {
            for ( int y = 0; y < height; y++ ) {
                if ( Random::Int( 5 ) < 2 || x == 0 || x == width - 1 || y == 0 || y == width - 1 )
                    front[ y * width + x ] = true;
                else
                    front[ y * width + x ] = false;
            }
        }

        // Run a generation or two
        int r2 = Random::Int( 2 ) + 2;
        for ( uint i = 0; i < 4; i++ ) {
            bool* t = front;
            front = back;
            back = t;

            for ( int x = 0; x < width; x++ ) {
                for ( int y = 0; y < height; y++ ) {
                    int n = 0, n2 = 0;
                    for ( int a = -2; a <= 2; a++ ) {
                        for ( int b = -2; b <= 2; b++ ) {
                            if ( x + a < 0 || x + a >= width || y + b < 0 || y + b >= height ||
                                 back[ ( y + b ) * width + ( x + a ) ] ) {
                                n2++;
                                if ( a >= -1 && a <= 1 && b >= -1 && b <= 1 )
                                    n++;
                            }
                        }
                    }
                    front[ y * width + x ] = n >= 5 || n2 < r2;
                }
            }
        }
        // Run a couple more with slightly different settings to refine it
        for ( uint i = 0; i < 3; i++ ) {
            bool* t = front;
            front = back;
            back = t;

            for ( int x = 0; x < width; x++ ) {
                for ( int y = 0; y < height; y++ ) {
                    int n = 0;
                    for ( int a = -1; a <= 1; a++ ) {
                        for ( int b = -1; b <= 1; b++ ) {
                            if ( x + a < 0 || x + a >= width || y + b < 0 || y + b >= height ||
                                 back[ ( y + b ) * width + ( x + a ) ] )
                                n++;
                        }
                    }
                    front[ y * width + x ] = n >= 5;
                }
            }
        }

        // Check there's at least something
        for ( int x = 0; x < width; x++ )
            for ( int y = 0; y < height; y++ )
                if ( !front[ y * width + x ] )
                    okay = true;

    }

    // Ensure connectivity
    bool first = true;
    for ( int x = 0; x < width; x++ ) {
        for ( int y = 0; y < height; y++ ) {
            if ( first && !front[ y * width + x ] ) {
                back[ y * width + x ] = true;
                first = false;
            }
            else
                back[ y * width + x ] = false;
        }
    }
    // Flood fill to find a connected area
    bool any = true;
    while ( any ) {
        any = false;
        for ( int x = 0; x < width; x++ ) {
            for ( int y = 0; y < height; y++ ) {
                if ( front[ y * width + x ] || back[ y * width + x ] )
                    continue;
                bool near = false;
                for ( int a = -1; a <= 1; a++ ) {
                    for ( int b = -1; b <= 1; b++ ) {
                        if ( y + b < 0 || y + b >= height || x + a < 0 || x + a >= width )
                            continue;
                        if ( back[ ( y + b ) * width + ( x + a ) ] ) {
                            near = true;
                            break;
                        }
                    }
                }
                if ( near ) {
                    back[ y * width + x ] = true;
                    any = true;
                }
            }
        }

        // If finished filling, check if there's any unconnected areas
        if ( !any ) {
            std::vector< std::pair< int, int > > cons;
            std::vector< std::pair< int, int > > uncons;
            for ( int x = 0; x < width; x++ ) {
                for ( int y = 0; y < height; y++ ) {
                    if ( front[ y * width + x ] )
                        continue;
                    if ( back[ y * width + x ] )
                        cons.push_back( std::pair< int, int >( x, y ) );
                    else
                        uncons.push_back( std::pair< int, int >( x, y ) );
                }
            }

            // If there is, connect something and start the whole thing over
            if ( !uncons.empty() ) {
                std::pair< int, int > a =   cons[ Random::Int(   cons.size() ) ];
                std::pair< int, int > b = uncons[ Random::Int( uncons.size() ) ];

                bool which = Random::Int( 2 ) == 0;
                for ( int x = std::min( a.first, b.first ); x <= std::max( a.first, b.first ); x++ ) {
                    front[ ( which ? a.second : b.second ) * width + x ] = false;
                }
                for ( int y = std::min( a.second, b.second ); y <= std::max( a.second, b.second ); y++ ) {
                    front[ y * width + ( which ? b.first : a.first ) ] = false;
                }

                bool first = true;
                for ( int x = 0; x < width; x++ ) {
                    for ( int y = 0; y < height; y++ ) {
                        if ( first && !front[ y * width + x ] ) {
                            back[ y * width + x ] = true;
                            first = false;
                        }
                        else
                            back[ y * width + x ] = false;
                    }
                }
                any = true;
            }
        }
    }

    // Draw to map
    for ( int x = 0; x < width; x++ ) {
        for ( int y = 0; y < height; y++ ) {
            if ( !front[ y * width + x ] )
                SetTile( x, y, Tile::FLOOR );
            else
                SetTile( x, y, Tile::ROCK );
        }
    }

    delete[] front;
    delete[] back;
}

void RBOrganicCave::BuildOrganic( int width, int height, int nMin, int nMax, int conChance )
{
    // (1, 2) - tight (labyrinth) mazes
    // (1, 3) - more open mazes
    // (1, 4) - caves with straight sections of wall within
    // (1, 5) - as above but more open
    // (1, 6) - open caves with protrusions from edges
    // (1, 7) - as above but smaller protrusions
    // (1, 8) - open caves with natural edges
    // (2, 2) - network of diagonal passages
    // (2, 3) - natural labyrinthy caves
    // (2, 4) - as above but more open
    // (2, 5) - open with protrusions
    // (2, 6) - more open
    // (2, 7) - open natural caves
    // (2, 8) - as above
    // (3, 4) - square caves
    // (3, 5) - open caves
    // (3, 6) - uniform sorta circular open areas
    // (3, 7) - natural open caves
    // (3, 8) - as above

    // Seed the map
    for ( int x = 0; x < width; x++ ) {
        for ( int y = 0; y < height; y++ ) {
            if ( x >= width / 2 - 1 && x <= width / 2 + 1 && y >= height / 2 - 1 && y <= height / 2 + 1 )
                SetTile( x, y, Tile::FLOOR );
            else
                SetTile( x, y, Tile::ROCK );
        }
    }
    // Find diggable cells
    std::vector< std::pair< int, int > > diggable;
    for ( int x = 1; x < width - 1; x++ ) {
        for ( int y = 1; y < height - 1; y++ ) {
            if ( ( GetTile( x - 1, y ) == Tile::FLOOR ||
                   GetTile( x + 1, y ) == Tile::FLOOR ||
                   GetTile( x, y - 1 ) == Tile::FLOOR ||
                   GetTile( x, y + 1 ) == Tile::FLOOR ) &&
                 GetTile( x, y ) != Tile::FLOOR )
                diggable.push_back( std::pair< int, int >( x, y ) );
        }
    }

    int tiles = 0;
    double fill = double( Random::Int( 31 ) ) / 100.0 + 0.2;
    while ( tiles < int( double( width * height ) * fill + 0.5 ) && !diggable.empty() ) {
        int r = Random::Int( diggable.size() );
        std::pair< int, int > t = diggable[ r ];
        diggable.erase( diggable.begin() + r );

        if ( GetTile( t.first, t.second ) == Tile::FLOOR )
            continue;

        // Find neighbours
        int map[ 3 ][ 3 ];
        for ( int x = 0; x < 3; x++ )
            for ( int y = 0; y < 3; y++ )
                map[ x ][ y ] = GetTile( t.first + x - 1, t.second + y - 1 ) != Tile::FLOOR &&
                                ( x != 1 || y != 1 ) ? 1 : 0;

        // Check if this would connect them
        int groups = 0;
        bool search = true;
        while ( search ) {
            bool go = false;
            while ( true ) {
                bool found = false;
                for ( int x = 0; x < 3; x++ ) {
                    for ( int y = 0; y < 3; y++ ) {
                        if ( map[ x ][ y ] == 1 && !go ) {
                            map[ x ][ y ] = 2;
                            go = true;
                            found = true;
                        }
                        if ( map[ x ][ y ] == 1 && go ) {
                            if ( ( x > 0 && map[ x - 1 ][ y ] == 2 ) ||
                                 ( x < 2 && map[ x + 1 ][ y ] == 2 ) ||
                                 ( y > 0 && map[ x ][ y - 1 ] == 2 ) ||
                                 ( y < 2 && map[ x ][ y + 1 ] == 2 ) ) {
                                map[ x ][ y ] = 2;
                                found = true;
                            }
                        }
                    }
                }
                if ( !found ) {
                    if ( go ) {
                        groups++;
                        for ( int x = 0; x < 3; x++ )
                            for ( int y = 0; y < 3; y++ )
                                if ( map[ x ][ y ] == 2 )
                                    map[ x ][ y ] = 0;
                        go = false;
                    }
                    else
                        search = false;
                    break;
                }
            }
        }
        if ( groups > 1 && Random::Int( 100 ) >= conChance )
            continue;

        // Count neighbours
        int n = 0;
        for ( int x = -1; x <= 1; x++ ) {
            for ( int y = -1; y <= 1; y++ ) {
                if ( x == 0 && y == 0 )
                    continue;
                if ( GetTile( t.first + x, t.second + y ) == Tile::FLOOR )
                    n++;
            }
        }
        // Dig
        if ( n >= nMin && n <= nMax ) {
            tiles++;
            SetTile( t.first, t.second, Tile::FLOOR );
            // Add diggable neighbours
            if ( t.first > 1 && GetTile( t.first - 1, t.second ) != Tile::FLOOR )
                diggable.push_back( std::pair< int, int >( t.first - 1, t.second ) );
            if ( t.second > 1 && GetTile( t.first, t.second - 1 ) != Tile::FLOOR )
                diggable.push_back( std::pair< int, int >( t.first, t.second - 1 ) );
            if ( t.first < width - 2 && GetTile( t.first + 1, t.second ) != Tile::FLOOR )
                diggable.push_back( std::pair< int, int >( t.first + 1, t.second ) );
            if ( t.second < height - 2 && GetTile( t.first, t.second + 1 ) != Tile::FLOOR )
                diggable.push_back( std::pair< int, int >( t.first, t.second + 1 ) );
        }
    }
}

void RBOrganicCave::GetRoomSize( int& width, int& height ) const
{
    width  = Random::Int( 8 ) + Random::Int( 8 ) + 8;
    height = Random::Int( 8 ) + Random::Int( 8 ) + 8;
}

void RBOrganicCave::BuildInternal( int width, int height )
{
    int r = Random::Int( 11 );
    int nMin, nMax;
    if ( r == 0 ) {
        nMin = 1;
        nMax = 6;
    }
    if ( r == 1 ) {
        nMin = 1;
        nMax = 7;
    }
    if ( r == 2 ) {
        nMin = 1;
        nMax = 8;
    }
    if ( r == 3 ) {
        nMin = 2;
        nMax = 5;
    }
    if ( r == 4 ) {
        nMin = 2;
        nMax = 6;
    }
    if ( r == 5 ) {
        nMin = 2;
        nMax = 7;
    }
    if ( r == 6 ) {
        nMin = 2;
        nMax = 8;
    }
    if ( r == 7 ) {
        nMin = 3;
        nMax = 5;
    }
    if ( r == 8 ) {
        nMin = 3;
        nMax = 6;
    }
    if ( r == 9 ) {
        nMin = 3;
        nMax = 7;
    }
    if ( r == 10 ) {
        nMin = 3;
        nMax = 8;
    }
    BuildOrganic( width, height, nMin, nMax, Random::Int( 10 ) );
}

void RBLabyrinth::GetRoomSize( int& width, int& height ) const
{
    width  = Random::Int( 8 ) + Random::Int( 8 ) + 8;
    height = Random::Int( 8 ) + Random::Int( 8 ) + 8;
}

void RBLabyrinth::BuildInternal( int width, int height )
{
    BuildOrganic( width, height, 1, Random::Int( 2 ) == 0 ? 2 : 3, Random::Int( 2 ) == 0 ? Random::Int( 10 ) : 0 );
}

void RBLabyrinthCave::GetRoomSize( int& width, int& height ) const
{
    width  = Random::Int( 16 ) + Random::Int( 16 ) + 8;
    height = Random::Int( 16 ) + Random::Int( 16 ) + 8;
}

void RBLabyrinthCave::BuildInternal( int width, int height )
{
    int nMax = 3;
    if ( Random::Int( 2 ) == 0 )
        nMax = 4;
    BuildOrganic( width, height, 2, nMax, Random::Int( 2 ) == 0 ? Random::Int( 10 ) : 0 );
}